/*
 * Ethan Levine
 * 
 * The contents of this file are subject to the GNU General Public
 * License (GPL) v2.  The full contents of this license can be
 * found at Mathma's website, http://www.codeplex.com/mathma/license
 */

package mathma.core.real;

import mathma.core.expr.Variable;
import mathma.core.expr.Expression;

import mathma.core.real.form.AdditionExpression;
import mathma.core.real.form.DivisionExpression;
import mathma.core.real.form.MultiplicationExpression;
import mathma.core.real.form.NegationExpression;
import mathma.core.real.form.PowerExpression;
import mathma.core.real.form.ReciprocalExpression;
import mathma.core.real.form.SubtractionExpression;

import mathma.core.simp.BasicSimplifier;
import mathma.core.simp.Simplifier;
import mathma.core.simp.TargettedSimplificationAlgorithm;

import mathma.core.value.Value;
import mathma.core.value.ValueType;

import java.math.BigDecimal;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>An abstract real number.  This number may be infinite or indeterminate.  Real
 * objects are immutable, so they are safe for threading.  When creating s new
 * finite Real, use a {@link FiniteReal FiniteReal} constructor.  When using
 * an infinite or indeterminate Real, reference the immutable static fields of
 * either {@link InfiniteReal InfiniteReal} or
 * {@link IndeterminateReal IndeterminateReal}, respectively.</p>
 *
 * <p>Reals are of arbitrary precision.  They are <i>exact</i>.  Unfortunately, a
 * handful of the methods produce irrational numbers, such as {@link #exp() exp()}.
 * Methods such as these are accurate to at least fifteen digits.</p>
 *
 * <p>When relevent, it is assumed that this Real is in radians.</p>
 *
 * @since 0.1.0
 * @author Ethan Levine
 */
public abstract class Real extends Value<Real> {

    public final static Real ZERO = new FiniteReal(0);
    public final static Real ONE = new FiniteReal(1);
    public final static Real TWO = new FiniteReal(2);
    public final static Real NEGATIVE_ONE = new FiniteReal(-1);

    public final static Simplifier Simplifier = new RealSimplifier();

    /**
     * The ValueType that represents a Real number.  This is used as an argument
     * to any subclasses of Expression that use Reals to back themselves.
     */
    public final static ValueType<Real> Type = new ValueType<Real>(Simplifier,
            Real.class);

    protected Real() {
        super(Real.Type);
    }

    /**
     * Gets the value of this Real as a rounded integer.
     *
     * @return The int value of this Real.
     */
    abstract public int intValue();

    /**
     * Gets the value of this Real as a rounded double.
     *
     * @return The double value of this Real.
     */
    abstract public double doubleValue();

    /**
     * Gets the value of this Real as a rounded short.
     *
     * @return The short value of this Real.
     */
    abstract public short shortValue();

    /**
     * Gets the value of this Real as a rounded long.
     *
     * @return The long value of this Real.
     */
    abstract public long longValue();

    /**
     * Gets the value of this Real as a rounded float.
     *
     * @return The float value of this Real.
     */
    abstract public float floatValue();

    /**
     * Gets the value of this Real as a rounded byte.
     *
     * @return The byte value of this Real.
     */
    abstract public byte byteValue();

    /**
     * Gets the value of this Real as a BigDecimal.  The returned BigDecimal
     * exactly matches the value of this Real.
     *
     * @return The BigDecimal value of this Real.
     */
    abstract public BigDecimal bigDecimalValue();

    /**
     * Returns <tt>true</tt> if this Real is either positive or negative infinity,
     * and <tt>false</tt> otherwise.
     *
     * @return Whether this Real is infinite.
     */
    abstract public boolean isInfinite();

    /**
     * Returns <tt>true</tt> if this Real is finite, and <tt>false</tt> otherwise.
     *
     * @return Whether this Real is finite.
     */
    abstract public boolean isFinite();

    /**
     * Returns <tt>true</tt> if this Real is indeterminate, and <tt>false</tt>
     * otherwise.
     *
     * @return Whether this Real is indeterminate.
     */
    abstract public boolean isIndeterminate();

    /**
     * Returns <tt>true</tt> if this Real is an integer, and <tt>false</tt>
     * otherwise.  A Real is classified as an integer if the remainder of division
     * by 1 is equal to 0.
     * 
     * @return Whether this Real is an integer.
     */
    abstract public boolean isInteger();

    /**
     * Returns <tt>true</tt> if this Real is negative, and <tt>false</tt> otherwise.
     * A Real is classified as negative if it is less than 0.
     * 
     * @return Whether this Real is negative.
     */
    abstract public boolean isNegative();

    /**
     * Returns <tt>true</tt> if this Real is positive, and <tt>false</tt> otherwise.
     * A Real is classified as positive if it is greater than 0.
     *
     * @return Whether this Real is positive.
     */
    abstract public boolean isPositive();

    /**
     * Returns <tt>true</tt> if this Real is less than the given Real, <tt>false</tt>
     * otherwise.  If this cannot be determined, <tt>false</tt> is returned.
     *
     * @param other The Real to compare this Real to.
     *
     * @return Whether this Real is less than the given Real.
     */
    abstract public boolean isLessThan(Real other);

    /**
     * Returns <tt>true</tt> if this Real is greater than the given Real, <tt>false</tt>
     * otherwise.  If this cannot be determined, <tt>false</tt> is returned.
     *
     * @param other The Real to compare this Real to.
     *
     * @return Whether this Real is greater than the given Real.
     */
    abstract public boolean isGreaterThan(Real other);

    /**
     * Returns <tt>true</tt> if this Real is less than or equal to the given Real,
     * <tt>false</tt> otherwise.  If this cannot be determined, <tt>false</tt>
     * is returned.
     *
     * @param other The Real to compare this Real to.
     *
     * @return Whether this Real is less than or equal to the given Real.
     */
    public boolean isLessThanOrEqual(Real other) {
        return !other.isIndeterminate() && !isIndeterminate() &&
                (isLessThan(other) || equals(other));
    }

    /**
     * Returns <tt>true</tt> if this Real is greater than or equal to the given Real,
     * <tt>false</tt> otherwise.  If this cannot be determined, <tt>false</tt>
     * is returned.
     *
     * @param other The Real to compare this Real to.
     *
     * @return Whether this Real is greater than or equal to the given Real.
     */
    public boolean isGreaterThanOrEqual(Real other) {
        return !other.isIndeterminate() && !isIndeterminate() &&
                (isGreaterThan(other) || equals(other));
    }
    
    @Override
    abstract public boolean equals(Object other);

    /**
     * Returns the negation of this Real.  This is equal to 0 - x where x = this.
     *
     * @return The negation of this Real.
     */
    abstract public Real negate();

    /**
     * Returns the absolute value of this Real.  This is equal to x where x =
     * this and x >= 0, and equal to -x otherwise.
     * 
     * @return The absolute value of this Real.
     */
    abstract public Real absoluteValue();

    /**
     * Returns the floor of this Real.  This is equal to this real rounded down.
     *
     * @return The floor of this Real.
     */
    abstract public Real floor();

    /**
     * Returns the ceiling of this Real.  This is equal to this real rounded up.
     *
     * @return The ceiling of this Real.
     */
    abstract public Real ceiling();

    /**
     * Returns the truncation of this Real.  This is equal to this real rounded
     * towards 0.
     *
     * @return The truncation of this Real.
     */
    abstract public Real truncate();

    /**
     * Rounds this Real to the nearest integer.  When the Real ends in .5, it is
     * rounded up.
     *
     * @return The rounded integer of this Real.
     */
    abstract public Real round();

    /**
     * Adds this Real to another Real.
     *
     * @param operand The Real to add.
     *
     * @return The addition of this Real and the given Real.
     */
    abstract public Real add(Real operand);

    /**
     * Subtracts this Real by a given Real.
     *
     * @param operand The Real to subtract from this Real.
     *
     * @return The subtraction of this Real by the given Real.
     */
    abstract public Real subtract(Real operand);

    /**
     * Multiplies this Real by another Real.
     *
     * @param operand The Real to multiply by.
     *
     * @return The multiplication of this Real and the given Real.
     */
    abstract public Real multiply(Real operand);

    /**
     * Divides this Real by another Real.  This Real is the numerator and the
     * other Real is the denominator.
     *
     * @param denominator The denominator in the division.
     *
     * @return The division of this Real by the given Real.
     */
    abstract public Real divideBy(Real denominator);

    /**
     * Subtracts this Real from a given Real.
     *
     * @param operand The Real to subtract this Real from.
     *
     * @return The subtraction of the given Real by this Real.
     */
    public Real subtractFrom(Real operand) {
        return operand.subtract(this);
    }

    /**
     * Divides another Real by this Real.  This Real is the denominator and the
     * other Real is the numerator.
     *
     * @param numerator The numerator in the division.
     *
     * @return The division of the given Real by this Real.
     */
    public Real divideInto(Real numerator) {
        return numerator.divideBy(this);
    }

    /**
     * Returns the trigonometric sine of this Real.  This is determined through repeated
     * division.
     *
     * @return The sine of this Real.
     */
    abstract public Real sine();

    /**
     * Returns the trigonometric cosine of this Real.
     *
     * @return The cosine of this Real.
     */
    abstract public Real cosine();

    /**
     * Returns the trigonometric tangent of this Real.
     *
     * @return The tangent of this Real.
     */
    abstract public Real tangent();

    /**
     * Returns the trigonometric secant of this Real.
     *
     * @return The secant of this Real.
     */
    abstract public Real secant();

    /**
     * Returns the trigonometric cosecant of this Real.
     *
     * @return The cosecant of this Real.
     */
    abstract public Real cosecant();

    /**
     * Returns the trigonometric cotangent of this Real.
     *
     * @return The cotangent of this Real.
     */
    abstract public Real cotangent();
    
    // TODO - Bind domain of these...
    
    /**
     * Returns the trigonometric inverse sine, or arcsine, of this Real.
     *
     * @return The inverse sine of this Real.
     */
    abstract public Real invSine();

    /**
     * Returns the trigonometric inverse cosine, or arccosine, of this Real.
     *
     * @return The inverse cosine of this Real.
     */
    abstract public Real invCosine();

    /**
     * Returns the trigonometric inverse tangent, or arctangent, of this Real.
     *
     * @return The inverse tangent of this Real.
     */
    abstract public Real invTangent();

    /**
     * Returns the trigonometric inverse secant, or arcsecant, of this Real.
     *
     * @return The inverse secant of this Real.
     */
    abstract public Real invSecant();

    /**
     * Returns the trigonometric inverse cosecant, or arccosecant, of this Real.
     *
     * @return The inverse cosecant of this Real.
     */
    abstract public Real invCosecant();

    /**
     * Returns the trigonometric inverse cotangent, or arccotangent, of this Real.
     *
     * @return The inverse cotangent of this Real.
     */
    abstract public Real invCotangent();

    /**
     * Returns the hyperbolic sine, or sinh, of this Real.
     *
     * @return The hyperbolic sine of this Real.
     */
    abstract public Real hyperSine();

    /**
     * Returns the hyperbolic cosine, or cosh, of this Real.
     *
     * @return The hyperbolic sine of this Real.
     */
    abstract public Real hyperCosine();

    /**
     * Returns the hyperbolic tangent, or tanh, of this Real.
     *
     * @return The hyperbolic tangent of this Real.
     */
    abstract public Real hyperTangent();

    /**
     * Returns the hyperbolic secant, or sech, of this Real.
     *
     * @return The hyperbolic secant of this Real.
     */
    abstract public Real hyperSecant();

    /**
     * Returns the hyperbolic cosecant, or csch, of this Real.
     *
     * @return The hyperbolic cosecant of this Real.
     */
    abstract public Real hyperCosecant();

    /**
     * Returns the hyperbolic cotangent, or coth, of this Real.
     *
     * @return The hyperbolic cotangent of this Real.
     */
    abstract public Real hyperCotangent();
    
    // TODO - Bind domain of these...

    /**
     * Returns the inverse hyperbolic sine, or arcsinh, of this Real.
     *
     * @return The invrse hyperbolic sine of this Real.
     */
    abstract public Real invHyperSine();

    /**
     * Returns the inverse hyperbolic cosine, or arccosh, of this Real.
     *
     * @return The invrse hyperbolic cosine of this Real.
     */
    abstract public Real invHyperCosine();

    /**
     * Returns the inverse hyperbolic tangent, or arctanh, of this Real.
     *
     * @return The invrse hyperbolic tangent of this Real.
     */
    abstract public Real invHyperTangent();

    /**
     * Returns the inverse hyperbolic secant, or arcsech, of this Real.
     *
     * @return The invrse hyperbolic secant of this Real.
     */
    abstract public Real invHyperSecant();

    /**
     * Returns the inverse hyperbolic cosecant, or arccsch, of this Real.
     *
     * @return The invrse hyperbolic cosecant of this Real.
     */
    abstract public Real invHyperCosecant();

    /**
     * Returns the inverse hyperbolic cotangent, or arccoth, of this Real.
     *
     * @return The invrse hyperbolic cotangent of this Real.
     */
    abstract public Real invHyperCotangent();

    /**
     * Returns the modulo of this Real by another Real.  This is roughly the
     * remainder of the divison of this Real by another Real.
     *
     * TODO - elaborate on definition of modulo.
     *
     * @param modulus The modulus of the modulo operation.
     *
     * @return The modulo of this Real by another Real.
     */
    abstract public Real modulo(Real modulus);

    /**
     * Returns the factorial of this Real.  This is <i>not</i> the gamma function,
     * and thus only works on integers.
     *
     * @return The factorial of this Real.
     */
    abstract public Real factorial();

    /**
     * Raises the mathematical constant e to the power of this Real.  This is
     * determined through repeated addition.
     *
     * @return e to the power of this Real.
     * @see mathma.core.expr.RealConstant#E
     */
    abstract public Real exp();

    /**
     * Returns the natural logarithm of this Real, or the logarithm of this Real
     * with base e.  This is determined through repeated addition.
     *
     * @return The natural logarithm of this Real.
     */
    abstract public Real ln();

    /**
     * Returns the square root of this Real.  Since the result is a Real, the
     * domain is only non-negative Reals.
     *
     * @return The square root of this Real.
     */
    abstract public Real sqrt();

    public Real square() {
        return this.multiply(this);
    }

    /**
     * Raises this Real to the power of another Real.
     *
     * @param exponent The exponent to raise to.
     *
     * @return This Real raised to the power of another Real.
     */
    abstract public Real power(Real exponent);
    
    @Override
    public Expression<Real> differentiate(Variable<Real> var) {
        return Real.ZERO;
    }

    // TODO - Finish implementing RealSimplifier.
    private static class RealSimplifier extends BasicSimplifier {

        private RealSimplifier() {
            super();
            addAlgorithms(
                    new ReplaceSubtractionDivisionNegation(),
                    new UnnestAdditionAndMultiplication(),
                    new ReplaceReciprocalsWithPower(),
                    new ExpandMultiplicationPowers());
        }

        private class ReplaceSubtractionDivisionNegation
                extends TargettedSimplificationAlgorithm {

            public ReplaceSubtractionDivisionNegation() {
                super();
                mapFragment(SubtractionExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> expr) {
                        return new AdditionExpression(((SubtractionExpression) expr).getFirstOperand(), new MultiplicationExpression(
                                Real.NEGATIVE_ONE, ((SubtractionExpression) expr).getSecondOperand()));
                    }
                });
                mapFragment(DivisionExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> expr) {
                        return new MultiplicationExpression(
                                ((DivisionExpression) expr).getFirstOperand(),
                                new ReciprocalExpression(((DivisionExpression) expr).getSecondOperand()));
                    }
                });
                mapFragment(NegationExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> expr) {
                        return new MultiplicationExpression(Real.NEGATIVE_ONE,
                                ((NegationExpression) expr).getOperand());
                    }
                });
            }
        }

        private class UnnestAdditionAndMultiplication
                extends TargettedSimplificationAlgorithm {

            public UnnestAdditionAndMultiplication() {
                super();
                mapFragment(AdditionExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> rawExpr) {
                        AdditionExpression expr = (AdditionExpression) rawExpr;
                        List<Expression<Real>> operands = expr.getOperands();
                        List<Expression<Real>> newOperands = new ArrayList<Expression<Real>>();
                        for (Expression<Real> operand : operands) {
                            if (operand.getClass().equals(AdditionExpression.class)) {
                                List<Expression<Real>> simpNest = ((AdditionExpression) simp(operand)).getOperands();
                                for (Expression<Real> simpOperand : simpNest) {
                                    newOperands.add(simpOperand);
                                }
                            } else {
                                newOperands.add(operand);
                            }
                        }
                        return new AdditionExpression(newOperands);
                    }
                });
                mapFragment(MultiplicationExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> rawExpr) {
                        MultiplicationExpression expr = (MultiplicationExpression) rawExpr;
                        List<Expression<Real>> operands = expr.getOperands();
                        List<Expression<Real>> newOperands = new ArrayList<Expression<Real>>();
                        for (Expression<Real> operand : operands) {
                            if (operand.getClass().equals(MultiplicationExpression.class)) {
                                List<Expression<Real>> simpNest = ((MultiplicationExpression) simp(operand)).getOperands();
                                for (Expression<Real> simpOperand : simpNest) {
                                    newOperands.add(simpOperand);
                                }
                            } else {
                                newOperands.add(operand);
                            }
                        }
                        return new MultiplicationExpression(newOperands);
                    }
                });
            }
        }

        private class ReplaceReciprocalsWithPower
                extends TargettedSimplificationAlgorithm {

            public ReplaceReciprocalsWithPower() {
                super();
                mapFragment(ReciprocalExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> expr) {
                        return new PowerExpression(((ReciprocalExpression) expr).getOperand(), Real.NEGATIVE_ONE);
                    }
                });
            }
        }

        private class ExpandMultiplicationPowers
                extends TargettedSimplificationAlgorithm {

            public ExpandMultiplicationPowers() {
                super();
                mapFragment(PowerExpression.class, new Fragment<Real>() {

                    @Override
                    public Expression<Real> simp(Expression<Real> expr) {
                        if (((PowerExpression) expr).getFirstOperand().getClass().equals(MultiplicationExpression.class)) {
                            List<Expression<Real>> powerOprs =
                                    new ArrayList<Expression<Real>>();
                            List<Expression<Real>> multOprs =
                                    ((MultiplicationExpression) ((PowerExpression) expr).getFirstOperand()).getOperands();
                            for (Expression<Real> multOpr : multOprs) {
                                powerOprs.add(new PowerExpression(multOpr,
                                        ((PowerExpression) expr).getSecondOperand()));
                            }
                            return new MultiplicationExpression(powerOprs);
                        } else {
                            return expr;
                        }
                    }
                });
            }
        }
    }
}
